Skip to content

feat(x-markdown): add markdown rendering support and tests#2412

Merged
Sherry-hue merged 1 commit intolynx-family:mainfrom
Sherry-hue:p/hw/support-x-markdown
Apr 20, 2026
Merged

feat(x-markdown): add markdown rendering support and tests#2412
Sherry-hue merged 1 commit intolynx-family:mainfrom
Sherry-hue:p/hw/support-x-markdown

Conversation

@Sherry-hue
Copy link
Copy Markdown
Collaborator

@Sherry-hue Sherry-hue commented Apr 1, 2026

Summary by CodeRabbit

  • New Features

    • Added an opt-in x-markdown component for rich Markdown: headings, lists, tables, images, inline views, sanitization, truncation/"Read more", incremental updates, and customizable styles with bundled stylesheet.
    • Typewriter animation with configurable cursor, runtime controls, selection helpers, and programmatic selection APIs.
    • Emits events for link clicks, image taps, animation lifecycle, and selection changes.
  • Tests

    • Added comprehensive Playwright fixtures and tests for rendering, interactions, animations, selection, and sanitization.

Checklist

  • Tests updated (or not required).
  • Documentation updated (or not required).
  • Changeset added, and when a BREAKING CHANGE occurs, it needs to be clearly marked (or not required).

@Sherry-hue Sherry-hue requested a review from PupilTong as a code owner April 1, 2026 08:20
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 1, 2026

🦋 Changeset detected

Latest commit: b382f1e

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 10 packages
Name Type
@lynx-js/web-elements Patch
@lynx-js/web-core Patch
upgrade-rspeedy Patch
@lynx-js/web-rsbuild-server-middleware Patch
@lynx-js/template-webpack-plugin Patch
@lynx-js/react-rsbuild-plugin Patch
create-rspeedy Patch
@lynx-js/web-worker-rpc Patch
@lynx-js/react-alias-rsbuild-plugin Patch
@lynx-js/rspeedy Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 1, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a new x-markdown web component (implementation, attributes/controller, template, styles), wires exports and bundle, declares runtime deps, and introduces many HTML fixtures plus Playwright tests and a changeset; updates Playwright guidance for web-elements testing. (50 words)

Changes

Cohort / File(s) Summary
Core component
packages/web-platform/web-elements/src/elements/XMarkdown/XMarkdown.ts, packages/web-platform/web-elements/src/elements/XMarkdown/XMarkdownAttributes.ts, packages/web-platform/web-elements/src/elements/XMarkdown/index.ts
New XMarkdown custom element and attribute/controller module implementing markdown parsing/sanitization, incremental/typewriter rendering, selection APIs, event delegation for links/images, inline-view injection, animation controls, public helper methods, and lifecycle/observer wiring.
Templates & styles
packages/web-platform/web-elements/src/elements/htmlTemplates.ts, packages/web-platform/web-elements/src/elements/XMarkdown/x-markdown.css
Adds templateXMarkdown, annotates multiple templates as pure (/*#__PURE__*/), and adds x-markdown CSS (host + ::part(root)).
Package exports & bundle
packages/web-platform/web-elements/package.json, packages/web-platform/web-elements/index.css, packages/web-platform/web-elements/src/elements/all.ts
Adds ./XMarkdown export mapping (source/types/dist), introduces runtime deps dompurify and markdown-it (+ @types/markdown-it devDep), imports component CSS into bundle, and side-effect-imports new element in all.ts.
Tests & fixtures
packages/web-platform/web-elements/tests/x-markdown.spec.ts, packages/web-platform/web-elements/tests/fixtures/x-markdown/*.html, packages/web-platform/web-elements/tests/fixtures/shell-project.ts
Adds comprehensive Playwright spec and ~20+ HTML fixtures covering rendering, events (bindlink, bindimageTap), selection/menu behavior, incremental updates, typewriter effects, truncation, inline views, tables, images, and updates test fixture imports.
APIs/utilities & wiring
packages/web-platform/web-constants/src/utils/setElementPropertyOrAttribute.ts, .../src/utils/index.ts, plus multiple mainthread/web-core files (createElementAPI.ts, registerSetNativePropsHandler.ts, createMainThreadGlobalThis.ts, web-core-wasm/package.json`)
Introduces setElementPropertyIfPresent / setElementPropertyOrAttribute utilities and replaces direct attribute-setting with these helpers across main-thread and wasm client handlers; adds new package workspace dependency.
Tests (react) & test harness
packages/web-platform/web-tests/tests/react.spec.ts, packages/web-platform/web-tests/tests/react/basic-element-x-markdown-markdown-style/index.jsx
Adds a React-driven test page and a Playwright test asserting markdown-style propagation and link color.
Repository metadata & CI guidance
.changeset/nasty-lizards-refuse.md, .github/lynx-stack.instructions.md
Adds a changeset for a patch release documenting x-markdown opt-in import and updates Playwright guidance for web-elements test placement and markdown-specific testing constraints.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested reviewers

  • PupilTong
  • colinaaa

Poem

🐇 I nibble lines of markdown by moonlit code,
Typewriter whispers make the letters load,
Shadow roots cradle links and pictures too,
Tests hop in to prove each render true,
A little rabbit cheers — new component queued!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly describes the main change: adding markdown rendering support (x-markdown component) and corresponding tests.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Nitpick comments (2)
packages/web-platform/web-elements/src/elements/XMarkdown/XMarkdownAttributes.ts (1)

512-531: Use boostedQueueMicrotask for the overflow measurement.

This post-render branch does DOM reads on the main thread but bypasses the package batching helper. Switching the overflow check to boostedQueueMicrotask keeps it aligned with the rest of the reactive pipeline and avoids unnecessary layout thrash.

As per coding guidelines, "Use boostedQueueMicrotask for DOM reads/writes to avoid synchronous layout thrashing".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/web-platform/web-elements/src/elements/XMarkdown/XMarkdownAttributes.ts`
around lines 512 - 531, Replace the direct queueMicrotask usage with the package
batching helper boostedQueueMicrotask for the post-render overflow measurement:
when this.#textMaxline > 0 schedule the DOM read inside boostedQueueMicrotask
(use the same root, the same overflow calculation root.scrollHeight >
root.clientHeight + 1, and the same dispatch using this.#dom.dispatchEvent and
this.#overflowEmitted flag) so the overflow check aligns with the reactive
pipeline and avoids layout thrash; ensure error handling around the check is
preserved.
packages/web-platform/web-elements/tests/x-markdown.spec.ts (1)

154-161: Replace fixed sleeps with state-based waits.

These assertions are pinned to internal timing constants (#appendFlushDelay and the typewriter speed), so they’ll get flaky as soon as CI is slower or those defaults change. Prefer waitForFunction / expect.poll against paragraph count, cursor presence, or the _drawEnd / _animationComplete flags that the fixtures already expose.

Also applies to: 233-269

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/web-platform/web-elements/tests/x-markdown.spec.ts` around lines 154
- 161, Tests in x-markdown.spec.ts use fixed waits (page.waitForTimeout) around
incremental rendering which makes them flaky; replace those sleeps in the
'should batch append by newline boundary' test (and other similar blocks around
lines 233-269) with state-based waits: after appendContent(page, 'Line 2') poll
for the desired condition using expect.poll or page.waitForFunction to check
getShadowCount(page, 'p') reaches 2 or to observe the component flags like
_drawEnd or _animationComplete on the element, and remove the hardcoded
20ms/80ms waits so the test waits for actual state changes instead of timing
assumptions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.changeset/nasty-lizards-refuse.md:
- Line 7: Replace the typo "x-markdonw" with the correct term "x-markdown" in
the release note text; locate the string "x-markdonw" in the changeset content
and update it to "x-markdown" so the release note reads correctly.

In @.github/lynx-stack.instructions.md:
- Around line 5-11: The guidance currently conflicts by requiring dedicated spec
files under packages/web-platform/web-elements/tests while still pointing
snapshot commits to web-elements.spec.ts-snapshots; update the wording to
instruct that snapshots should be committed alongside their dedicated spec files
(e.g., tests/x-webview.spec.ts -> tests/x-webview.spec.ts-snapshots) instead of
the monolithic web-elements.spec.ts-snapshots, and replace the example text to
explicitly recommend creating separate spec files like tests/x-webview.spec.ts
for new components.

In `@packages/web-platform/web-elements/index.css`:
- Line 16: Replace the `@import` url(...) usage with the repo's quoted-string
import notation to satisfy Stylelint: locate the line containing `@import`
url("./src/elements/XMarkdown/x-markdown.css"); in index.css and change it to
use a quoted string import (i.e., remove the url(...) wrapper so the stylesheet
is imported with quoted-path syntax).

In `@packages/web-platform/web-elements/src/elements/XMarkdown/x-markdown.css`:
- Around line 6-11: The host rule for x-markdown is using raw flex properties;
replace them with the Lynx linear-layout CSS variables to follow layout
conventions: set --lynx-display instead of display, --lynx-linear-orientation
instead of flex-direction, and include any needed weight variables like
--lynx-linear-weight, --lynx-linear-weight-sum, or --lynx-linear-weight-basis
for children as applicable; update the x-markdown selector (the host rule) to
use those variables and remove/avoid raw display/flex-direction/align-items
declarations so the component conforms to the Lynx linear-layout system.

In `@packages/web-platform/web-elements/src/elements/XMarkdown/XMarkdown.ts`:
- Around line 41-54: pauseAnimation currently only sets animation-velocity="0"
which the attribute layer ignores and leaves the typewriter interval running;
update pauseAnimation to (1) read and store the current animation-type (e.g.,
setAttribute('data-prev-animation-type', currentType)) and (2) actively stop the
animation by setting animation-type to a non-typewriter sentinel (e.g., 'none'
or ''), then set animation-velocity to '0'; update resumeAnimation to check for
that stored data-prev-animation-type and restore it (set animation-type back to
the saved value and remove the data-prev-animation-type attribute), and ensure
it sets a positive animation-velocity if needed so resume reliably restarts the
typewriter; apply changes to the pauseAnimation and resumeAnimation methods
referenced above.

In
`@packages/web-platform/web-elements/src/elements/XMarkdown/XMarkdownAttributes.ts`:
- Around line 756-778: The helper `#injectInlineViews` currently moves the
original source node from the light DOM into the shadow by calling
container.appendChild(view) (and similarly for the cursor-injection path around
lines 844-850), which consumes the source node so it cannot be re-used on
subsequent renders; change both places to append a deep clone of the source
(e.g. view.cloneNode(true) or document.importNode(view, true)) into container
instead of the original, leaving the original node in the light DOM (or cache a
cloned copy if you need reuse) so the same inline view/custom cursor can be
injected repeatedly without losing the source node.
- Around line 663-666: The attribute handlers like _handleMarkdownStyle (which
calls `#applyMarkdownStyle`) need to trigger a rerender/post-render policy
reapplication because updates to markdown-style, content-complete,
typewriter-dynamic-height, typewriter-height-transition-duration, and
text-maxline are currently latent; update each handler to request a render and
then call `#applyPostRenderPolicies`() after the DOM update (e.g., via
requestUpdate/await updateComplete or scheduling a microtask) so the
cursor/truncation/post-render behavior is recalculated immediately when those
attributes change.
- Around line 368-375: When content is emptied in `#render`(), also fully reset
any in-flight typewriter state to avoid leaking timers/cursors into the next
content: in the branch that handles !this.#content, after clearing DOM and
calling this.#clearAppendFlushTimer(), call an existing typewriter-reset helper
(or add one) that clears the typewriter timer, removes/hides the cursor element,
resets typewriter counters/buffer (e.g. step/index/append remainder beyond
this.#appendRemainder), and clears any related flags; if no helper exists,
implement a private method like `#resetTypewriterState`() and invoke it from
`#render`().

---

Nitpick comments:
In
`@packages/web-platform/web-elements/src/elements/XMarkdown/XMarkdownAttributes.ts`:
- Around line 512-531: Replace the direct queueMicrotask usage with the package
batching helper boostedQueueMicrotask for the post-render overflow measurement:
when this.#textMaxline > 0 schedule the DOM read inside boostedQueueMicrotask
(use the same root, the same overflow calculation root.scrollHeight >
root.clientHeight + 1, and the same dispatch using this.#dom.dispatchEvent and
this.#overflowEmitted flag) so the overflow check aligns with the reactive
pipeline and avoids layout thrash; ensure error handling around the check is
preserved.

In `@packages/web-platform/web-elements/tests/x-markdown.spec.ts`:
- Around line 154-161: Tests in x-markdown.spec.ts use fixed waits
(page.waitForTimeout) around incremental rendering which makes them flaky;
replace those sleeps in the 'should batch append by newline boundary' test (and
other similar blocks around lines 233-269) with state-based waits: after
appendContent(page, 'Line 2') poll for the desired condition using expect.poll
or page.waitForFunction to check getShadowCount(page, 'p') reaches 2 or to
observe the component flags like _drawEnd or _animationComplete on the element,
and remove the hardcoded 20ms/80ms waits so the test waits for actual state
changes instead of timing assumptions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 696d21df-87b6-4e4e-9be7-05f81345e0c2

📥 Commits

Reviewing files that changed from the base of the PR and between 547a88b and 56fd8e8.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (23)
  • .changeset/nasty-lizards-refuse.md
  • .github/lynx-stack.instructions.md
  • packages/web-platform/web-elements/index.css
  • packages/web-platform/web-elements/package.json
  • packages/web-platform/web-elements/src/elements/XMarkdown/XMarkdown.ts
  • packages/web-platform/web-elements/src/elements/XMarkdown/XMarkdownAttributes.ts
  • packages/web-platform/web-elements/src/elements/XMarkdown/index.ts
  • packages/web-platform/web-elements/src/elements/XMarkdown/x-markdown.css
  • packages/web-platform/web-elements/src/elements/htmlTemplates.ts
  • packages/web-platform/web-elements/tests/fixtures/shell-project.ts
  • packages/web-platform/web-elements/tests/fixtures/x-markdown/basic.html
  • packages/web-platform/web-elements/tests/fixtures/x-markdown/events.html
  • packages/web-platform/web-elements/tests/fixtures/x-markdown/image.html
  • packages/web-platform/web-elements/tests/fixtures/x-markdown/incremental.html
  • packages/web-platform/web-elements/tests/fixtures/x-markdown/inlineview.html
  • packages/web-platform/web-elements/tests/fixtures/x-markdown/style.html
  • packages/web-platform/web-elements/tests/fixtures/x-markdown/table.html
  • packages/web-platform/web-elements/tests/fixtures/x-markdown/truncate.html
  • packages/web-platform/web-elements/tests/fixtures/x-markdown/typewriter-cursor.html
  • packages/web-platform/web-elements/tests/fixtures/x-markdown/typewriter-keep-cursor.html
  • packages/web-platform/web-elements/tests/fixtures/x-markdown/typewriter-trailing-text.html
  • packages/web-platform/web-elements/tests/fixtures/x-markdown/typewriter.html
  • packages/web-platform/web-elements/tests/x-markdown.spec.ts

Comment thread .changeset/nasty-lizards-refuse.md Outdated
Comment thread .github/lynx-stack.instructions.md Outdated
Comment thread packages/web-platform/web-elements/index.css
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (5)
packages/web-platform/web-elements/tests/x-markdown.spec.ts (2)

433-441: Timing-dependent test may be flaky.

The test uses waitForTimeout(20) and waitForTimeout(80) to verify batch append behavior by newline boundary. These fixed delays can cause flakiness across different CI environments or under load. Consider using waitForFunction with a condition check instead, or document why these specific timing values are required.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/web-platform/web-elements/tests/x-markdown.spec.ts` around lines 433
- 441, The test 'should batch append by newline boundary' is flaky due to fixed
sleeps (page.waitForTimeout(20/80)); replace those timing-based assertions with
a polling wait that checks the actual condition instead—use Playwright's
page.waitForFunction or an expect.poll-style retry to wait until
getShadowCount(page, 'p') equals the expected value after calling
appendContent(page, 'Line 2') (first assert it stays 1 within a short window,
then wait for it to become 2), referencing the existing helpers goto,
appendContent, and getShadowCount to perform the condition checks rather than
fixed timeouts.

510-530: Multiple timing-dependent waits in typewriter cursor test.

The test uses waitForTimeout(1500) and waitForTimeout(1000) to verify cursor rendering and removal. These long delays make the test slow and potentially flaky. Consider exposing a test-only signal or using waitForFunction to poll for cursor presence/absence instead.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/web-platform/web-elements/tests/x-markdown.spec.ts` around lines 510
- 530, Replace the two hard-coded waits in the test 'should render custom
typewriter cursor' with deterministic polling: after navigating with goto and
waiting for window._drawStart, use page.waitForFunction to wait until the
shadowRoot querySelector('#cursor') returns truthy (cursor present) and then use
another page.waitForFunction to wait until that same selector returns falsy
(cursor removed); remove the waitForTimeout(1500) and waitForTimeout(1000) calls
and keep the existing queries (document.querySelector('x-markdown')!,
el.shadowRoot, '#cursor') so the assertions remain the same but are driven by
waitForFunction instead of fixed delays.
packages/web-platform/web-elements/src/elements/XMarkdown/XMarkdown.ts (1)

16-91: Significant code duplication with XMarkdownAttributes.ts.

The helper functions getComposedRange, createRangeByOffsets, and isSelectionNodeInsideHost are duplicated between this file and XMarkdownAttributes.ts (lines 162-237). Consider extracting these into a shared utility module to reduce maintenance burden and ensure consistent behavior.

♻️ Suggested approach

Create a shared utility file like XMarkdownSelectionUtils.ts:

// XMarkdownSelectionUtils.ts
export const getComposedRange = (
  selection: Selection,
  shadowRoot: ShadowRoot | null,
): StaticRange | null => { /* ... */ };

export const createRangeByOffsets = (
  doc: Document,
  root: HTMLElement,
  start: number,
  end: number,
): Range | null => { /* ... */ };

export const isSelectionNodeInsideHost = (
  dom: HTMLElement,
  shadowRoot: ShadowRoot | null,
  node: Node | null,
): boolean => { /* ... */ };

Then import in both XMarkdown.ts and XMarkdownAttributes.ts.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/web-platform/web-elements/src/elements/XMarkdown/XMarkdown.ts`
around lines 16 - 91, The three duplicated helpers (getComposedRange,
createRangeByOffsets, isSelectionNodeInsideHost) should be extracted into a
single shared module and imported where needed: create a new
XMarkdownSelectionUtils.ts exporting those three functions (keeping their
current signatures and behavior), replace the local definitions in XMarkdown.ts
and XMarkdownAttributes.ts with imports from XMarkdownSelectionUtils, and update
any references to use the imported symbols so both files use the same
implementation.
packages/web-platform/web-elements/src/elements/XMarkdown/XMarkdownAttributes.ts (2)

1028-1054: Typewriter timer interval recalculates on each restart but animation step persists.

When #startTypewriterTimer() is called (e.g., from velocity change on line 1147), it stops and restarts the timer with a new interval. However, if #animationStep has already reached #maxAnimationStep, the interval callback immediately stops again on line 1051. This is correct behavior, but the re-creation of the interval is wasteful. Consider guarding against restart when animation is already complete.

♻️ Suggested improvement
   `@registerAttributeHandler`('animation-velocity', true)
   _handleAnimationVelocity(newVal: string | null) {
     const n = Number(newVal);
     if (!Number.isNaN(n) && n > 0) this.#animationVelocity = n;
-    if (this.#animationType === 'typewriter' && this.#animationTimer) {
+    if (
+      this.#animationType === 'typewriter'
+      && this.#animationTimer
+      && this.#animationStep < this.#maxAnimationStep
+    ) {
       this.#startTypewriterTimer();
     }
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/web-platform/web-elements/src/elements/XMarkdown/XMarkdownAttributes.ts`
around lines 1028 - 1054, The timer restart in `#startTypewriterTimer`() should be
avoided when the animation is already complete; add an early guard at the top of
`#startTypewriterTimer`() that returns immediately if this.#animationStep >=
this.#maxAnimationStep (before calling `#stopTypewriterTimer` or creating a new
setInterval), so you don't recreate an interval only to stop it immediately;
keep the rest of the function (interval calculation, dispatch,
`#renderTypewriter`, and `#stopTypewriterTimer` usage) unchanged.

543-577: Event listeners should be scoped to the component's document.

The selection event listeners are attached to document (lines 571-574), but if this component is used within an iframe or different document context, using this.#dom.ownerDocument would be more robust.

♻️ Suggested improvement
   `#ensureEvents`() {
     if (this.#eventsAttached) return;
     const root = this.#root();
+    const doc = this.#dom.ownerDocument;
     root.addEventListener('click', this.#handleClick);
     this.#eventsAttached = true;
     if (!this.#selectionEventAttached) {
-      document.addEventListener('selectionchange', this.#handleSelectionChange);
-      document.addEventListener('mouseup', this.#handleSelectionGestureEnd);
-      document.addEventListener('touchend', this.#handleSelectionGestureEnd);
-      document.addEventListener('keyup', this.#handleSelectionGestureEnd);
+      doc.addEventListener('selectionchange', this.#handleSelectionChange);
+      doc.addEventListener('mouseup', this.#handleSelectionGestureEnd);
+      doc.addEventListener('touchend', this.#handleSelectionGestureEnd);
+      doc.addEventListener('keyup', this.#handleSelectionGestureEnd);
       this.#selectionEventAttached = true;
     }
   }

Apply the same change in dispose().

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/web-platform/web-elements/src/elements/XMarkdown/XMarkdownAttributes.ts`
around lines 543 - 577, The selection-related listeners are being added/removed
on the global document which breaks when the component lives in another document
(e.g. an iframe); update `#ensureEvents` and dispose to use the component's owner
document (use this.#dom.ownerDocument or the document obtained via the same
logic used by `#root`()) when calling addEventListener/removeEventListener for
`#handleSelectionChange` and `#handleSelectionGestureEnd`, ensuring you use the same
ownerDocument reference in both attachment and cleanup and keep the existing
flags (`#selectionEventAttached`) and timeout clearing logic unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/web-platform/web-elements/src/elements/XMarkdown/XMarkdown.ts`:
- Around line 16-91: The three duplicated helpers (getComposedRange,
createRangeByOffsets, isSelectionNodeInsideHost) should be extracted into a
single shared module and imported where needed: create a new
XMarkdownSelectionUtils.ts exporting those three functions (keeping their
current signatures and behavior), replace the local definitions in XMarkdown.ts
and XMarkdownAttributes.ts with imports from XMarkdownSelectionUtils, and update
any references to use the imported symbols so both files use the same
implementation.

In
`@packages/web-platform/web-elements/src/elements/XMarkdown/XMarkdownAttributes.ts`:
- Around line 1028-1054: The timer restart in `#startTypewriterTimer`() should be
avoided when the animation is already complete; add an early guard at the top of
`#startTypewriterTimer`() that returns immediately if this.#animationStep >=
this.#maxAnimationStep (before calling `#stopTypewriterTimer` or creating a new
setInterval), so you don't recreate an interval only to stop it immediately;
keep the rest of the function (interval calculation, dispatch,
`#renderTypewriter`, and `#stopTypewriterTimer` usage) unchanged.
- Around line 543-577: The selection-related listeners are being added/removed
on the global document which breaks when the component lives in another document
(e.g. an iframe); update `#ensureEvents` and dispose to use the component's owner
document (use this.#dom.ownerDocument or the document obtained via the same
logic used by `#root`()) when calling addEventListener/removeEventListener for
`#handleSelectionChange` and `#handleSelectionGestureEnd`, ensuring you use the same
ownerDocument reference in both attachment and cleanup and keep the existing
flags (`#selectionEventAttached`) and timeout clearing logic unchanged.

In `@packages/web-platform/web-elements/tests/x-markdown.spec.ts`:
- Around line 433-441: The test 'should batch append by newline boundary' is
flaky due to fixed sleeps (page.waitForTimeout(20/80)); replace those
timing-based assertions with a polling wait that checks the actual condition
instead—use Playwright's page.waitForFunction or an expect.poll-style retry to
wait until getShadowCount(page, 'p') equals the expected value after calling
appendContent(page, 'Line 2') (first assert it stays 1 within a short window,
then wait for it to become 2), referencing the existing helpers goto,
appendContent, and getShadowCount to perform the condition checks rather than
fixed timeouts.
- Around line 510-530: Replace the two hard-coded waits in the test 'should
render custom typewriter cursor' with deterministic polling: after navigating
with goto and waiting for window._drawStart, use page.waitForFunction to wait
until the shadowRoot querySelector('#cursor') returns truthy (cursor present)
and then use another page.waitForFunction to wait until that same selector
returns falsy (cursor removed); remove the waitForTimeout(1500) and
waitForTimeout(1000) calls and keep the existing queries
(document.querySelector('x-markdown')!, el.shadowRoot, '#cursor') so the
assertions remain the same but are driven by waitForFunction instead of fixed
delays.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: cfd0f3e2-2ade-4307-b842-5ea99892f56e

📥 Commits

Reviewing files that changed from the base of the PR and between 56fd8e8 and 82be98b.

📒 Files selected for processing (11)
  • packages/web-platform/web-elements/package.json
  • packages/web-platform/web-elements/src/elements/XMarkdown/XMarkdown.ts
  • packages/web-platform/web-elements/src/elements/XMarkdown/XMarkdownAttributes.ts
  • packages/web-platform/web-elements/src/elements/all.ts
  • packages/web-platform/web-elements/src/elements/htmlTemplates.ts
  • packages/web-platform/web-elements/tests/fixtures/x-markdown/html-tags.html
  • packages/web-platform/web-elements/tests/fixtures/x-markdown/menu.html
  • packages/web-platform/web-elements/tests/fixtures/x-markdown/table.html
  • packages/web-platform/web-elements/tests/fixtures/x-markdown/text-selection.html
  • packages/web-platform/web-elements/tests/fixtures/x-markdown/typewriter-effect.html
  • packages/web-platform/web-elements/tests/x-markdown.spec.ts
✅ Files skipped from review due to trivial changes (4)
  • packages/web-platform/web-elements/src/elements/all.ts
  • packages/web-platform/web-elements/tests/fixtures/x-markdown/table.html
  • packages/web-platform/web-elements/tests/fixtures/x-markdown/html-tags.html
  • packages/web-platform/web-elements/tests/fixtures/x-markdown/typewriter-effect.html
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/web-platform/web-elements/package.json
  • packages/web-platform/web-elements/src/elements/htmlTemplates.ts

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/web-platform/web-elements/src/elements/XMarkdown/XMarkdownAttributes.ts (1)

565-576: Consider lazy attachment of selection event listeners.

Selection-related document event listeners (selectionchange, mouseup, touchend, keyup) are attached unconditionally in #ensureEvents(), even when text-selection is disabled. While the handlers early-return when #textSelection is false, this adds unnecessary overhead.

Consider attaching these listeners only when text-selection becomes truthy and removing them when it becomes falsy in _handleTextSelection.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/web-platform/web-elements/src/elements/XMarkdown/XMarkdownAttributes.ts`
around lines 565 - 576, The selection-related document listeners are being
attached unconditionally in `#ensureEvents`; change that so `#ensureEvents` only
attaches the local click listener (root.addEventListener('click',
this.#handleClick)) and does not attach selection listeners, then implement lazy
attach/detach of selection listeners inside _handleTextSelection: when
this.#textSelection becomes truthy, add document listeners for 'selectionchange'
-> this.#handleSelectionChange and for 'mouseup', 'touchend', 'keyup' ->
this.#handleSelectionGestureEnd and set this.#selectionEventAttached = true;
when this.#textSelection becomes falsy, remove those same document listeners and
set this.#selectionEventAttached = false. Keep using the existing handler names
(`#handleSelectionChange`, `#handleSelectionGestureEnd`) and the flag
(`#selectionEventAttached`) to avoid duplicate attachments.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@packages/web-platform/web-elements/src/elements/XMarkdown/XMarkdownAttributes.ts`:
- Around line 565-576: The selection-related document listeners are being
attached unconditionally in `#ensureEvents`; change that so `#ensureEvents` only
attaches the local click listener (root.addEventListener('click',
this.#handleClick)) and does not attach selection listeners, then implement lazy
attach/detach of selection listeners inside _handleTextSelection: when
this.#textSelection becomes truthy, add document listeners for 'selectionchange'
-> this.#handleSelectionChange and for 'mouseup', 'touchend', 'keyup' ->
this.#handleSelectionGestureEnd and set this.#selectionEventAttached = true;
when this.#textSelection becomes falsy, remove those same document listeners and
set this.#selectionEventAttached = false. Keep using the existing handler names
(`#handleSelectionChange`, `#handleSelectionGestureEnd`) and the flag
(`#selectionEventAttached`) to avoid duplicate attachments.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 855cd20c-aebd-48c5-8114-815071842123

📥 Commits

Reviewing files that changed from the base of the PR and between 82be98b and 4bb3cb5.

📒 Files selected for processing (3)
  • packages/web-platform/web-elements/src/elements/XMarkdown/XMarkdownAttributes.ts
  • packages/web-platform/web-elements/tests/fixtures/x-markdown/inlineview-class.html
  • packages/web-platform/web-elements/tests/x-markdown.spec.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/web-platform/web-elements/tests/fixtures/x-markdown/inlineview-class.html
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/web-platform/web-elements/tests/x-markdown.spec.ts

@Sherry-hue Sherry-hue force-pushed the p/hw/support-x-markdown branch from 4bb3cb5 to a56b8d3 Compare April 9, 2026 06:13
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (6)
.changeset/nasty-lizards-refuse.md (1)

7-7: ⚠️ Potential issue | 🟡 Minor

Fix the component name typo.

x-markdonw should be x-markdown in the release note and import guidance.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.changeset/nasty-lizards-refuse.md at line 7, Update the release note and
import guidance to correct the component name typo: replace all occurrences of
the incorrect identifier "x-markdonw" with the correct component name
"x-markdown" so imports and documentation reference the proper opt-in importing
pattern and component usage.
packages/web-platform/web-elements/index.css (1)

16-16: ⚠️ Potential issue | 🟡 Minor

Use the repo's quoted import notation.

The added import still trips the import-notation Stylelint rule on this PR. Switch just this line to the quoted form to keep the change lint-clean.

Suggested fix
-@import url("./src/elements/XMarkdown/x-markdown.css");
+@import "./src/elements/XMarkdown/x-markdown.css";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/web-platform/web-elements/index.css` at line 16, The `@import` line
using url(...) triggers the import-notation Stylelint rule; replace the
statement `@import` url("./src/elements/XMarkdown/x-markdown.css"); in index.css
with the repo's quoted import notation by using a plain quoted import string for
"./src/elements/XMarkdown/x-markdown.css" (i.e., `@import`
"./src/elements/XMarkdown/x-markdown.css";) so the import-notation rule no
longer flags it and the change is lint-clean.
packages/web-platform/web-elements/src/elements/XMarkdown/XMarkdownAttributes.ts (3)

1255-1259: ⚠️ Potential issue | 🟠 Major

Don't move the source truncation/cursor node out of light DOM.

Both paths appendChild() the original node into the shadow tree. After the marker/cursor is removed or the component rerenders, that source node is gone, so the same truncation view or custom cursor can't be reused on later updates. Append a deep clone or slot the source node instead.

Also applies to: 1300-1305

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/web-platform/web-elements/src/elements/XMarkdown/XMarkdownAttributes.ts`
around lines 1255 - 1259, The code moves the original truncation/cursor node
from light DOM into the shadow by using span.appendChild(view), which removes it
from its source and prevents reuse; instead, when (cfg as any).truncationType
=== 'view' and a view node is found via this.#dom.querySelector(String((cfg as
any).content)), append a deep clone (e.g., view.cloneNode(true)) or slot the
original into the shadow rather than calling appendChild(view) so the source
node remains in light DOM and can be reused across rerenders; update the logic
around span.appendChild(view) (and the analogous block around lines 1300-1305)
to use cloning or a slot mechanism and ensure any event listeners or references
are correctly handled for the cloned element.

661-668: ⚠️ Potential issue | 🟠 Major

Reset typewriter state when content becomes empty.

This branch clears the DOM, but it leaves any active timer/cursor/step state behind. Clearing content mid-animation can keep the old interval alive and leak stale cursor state into the next render.

Possible fix
   if (!this.#content) {
+    this.#stopTypewriterTimer();
+    this.#removeTypewriterCursor();
+    this.#animationStarted = false;
+    this.#animationStep = 0;
+    this.#maxAnimationStep = 0;
     root.innerHTML = '';
     this.#renderedContent = '';
     this.#appendRemainder = '';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/web-platform/web-elements/src/elements/XMarkdown/XMarkdownAttributes.ts`
around lines 661 - 668, When content is cleared in `#render`(), you must also
reset the typewriter animation state so timers and cursor/step state don't
persist; in addition to clearing DOM and setting `#renderedContent` and
`#appendRemainder`, call the internal reset/stop logic (e.g. invoke any existing
method like `#clearAppendFlushTimer` and also stop/clear the typewriter timer,
reset cursor visibility and step counters — e.g. `#stopTypewriter`(), clear
`#appendFlushTimer`, set cursor/visibility flags and step/index fields to their
initial values) so no interval or cursor state leaks into the next render.

1111-1178: ⚠️ Potential issue | 🟠 Major

These runtime attribute updates are still latent until another render.

markdown-style, content-complete, typewriter-dynamic-height, typewriter-height-transition-duration, and text-maxline all feed cursor/truncation/post-render behavior, but these handlers only update cached state or the style tag. Updating them after first paint leaves the DOM in the old state until some unrelated content or animation tick reruns #applyPostRenderPolicies().

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/web-platform/web-elements/src/elements/XMarkdown/XMarkdownAttributes.ts`
around lines 1111 - 1178, Handlers _handleMarkdownStyle, _handleContentComplete,
_handleTypewriterDynamicHeight, _handleTypewriterHeightTransition, and
_handleTextMaxline only update cached state or style but do not trigger the
post-render fixes, leaving the DOM stale; after updating their internal state
and calling `#updateStyleTag`() (where present), call the component's post-render
routine (this.#applyPostRenderPolicies()) and/or schedule a render
(this.#scheduleRender()) so cursor/truncation/height behaviors are recomputed
immediately; modify each handler to invoke these methods (use
`#applyPostRenderPolicies`() first, then `#scheduleRender`() if needed) to force
reapplication of post-render policies when those attributes change at runtime.
packages/web-platform/web-elements/src/elements/XMarkdown/XMarkdown.ts (1)

193-205: ⚠️ Potential issue | 🟠 Major

pauseAnimation() / resumeAnimation() still don't control a live typewriter.

pauseAnimation() only writes animation-velocity="0", but XMarkdownAttributes._handleAnimationVelocity() ignores non-positive values and leaves the running interval alive. resumeAnimation() then rewrites animation-type="typewriter", which is ineffective when the element never left that mode, so this public API still has no reliable pause/resume path.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/web-platform/web-elements/src/elements/XMarkdown/XMarkdown.ts`
around lines 193 - 205, The pause/resume public API doesn't actually stop the
running typewriter because pauseAnimation only sets animation-velocity="0" while
XMarkdownAttributes._handleAnimationVelocity ignores non-positive values; update
pauseAnimation to actively stop the live typewriter by calling the internal
stop/cleanup routine (or clearing the interval/timer) used by
XMarkdown/XMarkdownAttributes (refer to the typewriter interval/stop logic and
any method like _handleAnimationVelocity, stopTypewriter,
clearTypewriterInterval), and update resumeAnimation to set a positive
animation-velocity (e.g., "40" or the provided initial-animation-step logic) and
then call the same internal start handler (or _handleAnimationVelocity) to
restart the interval; ensure resumeAnimation does not rely solely on toggling
animation-type and that both methods invoke the same internal start/stop code
paths so pause/resume is reliable.
🧹 Nitpick comments (2)
packages/web-platform/web-elements/tests/x-markdown.spec.ts (1)

349-350: Replace fixed sleeps with observable waits.

These assertions are coupled to the current timer values (50ms, 20/80ms, 500ms, 1500ms, 1000ms). They will get flaky as soon as CI load or animation timing changes. Prefer waitForFunction(...) or a Playwright assertion tied to the DOM state/event you actually need. Based on learnings, tests should use standard Playwright assertions like expect(locator)... for UI-state checks.

Also applies to: 437-440, 531-540, 552-552

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/web-platform/web-elements/tests/x-markdown.spec.ts` around lines 349
- 350, The test uses fixed sleeps (e.g., page.waitForTimeout(50)) after helper
selectShadowText which makes tests flaky; replace these waits with observable
waits that assert the actual DOM/selection state instead. Locate calls to
selectShadowText and the subsequent page.waitForTimeout (including the other
occurrences referenced) and change them to await page.waitForFunction(...) or
await expect(<appropriate Locator>).toHaveText/toHaveClass/visibility checks
tied to the exact DOM mutation or selection change you need (or use
locator.waitFor when waiting for an element state). Ensure the wait checks the
concrete condition produced by selectShadowText (selection range text, attribute
change, or element content) rather than sleeping for a fixed duration.
packages/web-platform/web-elements/src/elements/XMarkdown/XMarkdown.ts (1)

174-177: Use a cached genDomGetter for #markdown-root lookups.

This file repeatedly re-queries the same shadow node with ad-hoc null checks. A single genDomGetter keeps the access pattern consistent with the rest of the package and avoids repeating the same unsafe lookup logic. As per coding guidelines, "Use genDomGetter to safely access internal elements within the Shadow DOM".

Also applies to: 208-223, 301-307

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/web-platform/web-elements/src/elements/XMarkdown/XMarkdown.ts`
around lines 174 - 177, Multiple methods (including getImages) re-query
shadowRoot for '#markdown-root' with ad-hoc null checks; replace these with a
single cached genDomGetter instance to centralize safe Shadow DOM access. Add a
private cached getter (e.g., this._getMarkdownRoot =
genDomGetter('#markdown-root')) on the component and refactor getImages and the
other methods that query '#markdown-root' (the blocks around lines 208-223 and
301-307) to call that getter, handle null/HTMLElement typing consistently, and
remove the repeated shadowRoot?.querySelector logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/web-platform/web-elements/src/elements/XMarkdown/XMarkdown.ts`:
- Around line 311-340: calcOffset currently stops at the first direct text node
match and returns only that fragment’s offsets, under-reporting ranges for
elements whose text is split across multiple descendant text nodes; update
calcOffset (used by getParseResult and invoked for nodes from params.tags) to
walk all text nodes under root and, for each text node where
node.contains(current) (or current === node or current.parentNode === node), set
start on the first match and continuously update end to pos + len for every
matching descendant so the final start is the first text offset and end is the
last descendant text offset; keep the existing clamping to text.length and
ensure you still treat text nodes via Node.TEXT_NODE checks.
- Around line 217-266: The method getTextBoundingRect currently always treats
start/end as rendered-character indices; either implement proper support for
indexType: 'source' or remove that option from the public API. To implement
support, add a mapping step when params.indexType === 'source' that converts
source-string indices to rendered-character indices: create a helper (e.g.,
mapSourceToRenderedIndex) that walks the original markdown/source string and the
rendered text (walk text nodes under '#markdown-root'), skipping markdown syntax
characters and building a mapping from source positions to rendered char
offsets, then call createRangeByChar with the mapped start/end; reference
getTextBoundingRect, the params.indexType check, and createRangeByChar to locate
where to insert the mapping. If you prefer to remove the option, drop indexType
from the params and any callers/docs that rely on 'source'.

---

Duplicate comments:
In @.changeset/nasty-lizards-refuse.md:
- Line 7: Update the release note and import guidance to correct the component
name typo: replace all occurrences of the incorrect identifier "x-markdonw" with
the correct component name "x-markdown" so imports and documentation reference
the proper opt-in importing pattern and component usage.

In `@packages/web-platform/web-elements/index.css`:
- Line 16: The `@import` line using url(...) triggers the import-notation
Stylelint rule; replace the statement `@import`
url("./src/elements/XMarkdown/x-markdown.css"); in index.css with the repo's
quoted import notation by using a plain quoted import string for
"./src/elements/XMarkdown/x-markdown.css" (i.e., `@import`
"./src/elements/XMarkdown/x-markdown.css";) so the import-notation rule no
longer flags it and the change is lint-clean.

In `@packages/web-platform/web-elements/src/elements/XMarkdown/XMarkdown.ts`:
- Around line 193-205: The pause/resume public API doesn't actually stop the
running typewriter because pauseAnimation only sets animation-velocity="0" while
XMarkdownAttributes._handleAnimationVelocity ignores non-positive values; update
pauseAnimation to actively stop the live typewriter by calling the internal
stop/cleanup routine (or clearing the interval/timer) used by
XMarkdown/XMarkdownAttributes (refer to the typewriter interval/stop logic and
any method like _handleAnimationVelocity, stopTypewriter,
clearTypewriterInterval), and update resumeAnimation to set a positive
animation-velocity (e.g., "40" or the provided initial-animation-step logic) and
then call the same internal start handler (or _handleAnimationVelocity) to
restart the interval; ensure resumeAnimation does not rely solely on toggling
animation-type and that both methods invoke the same internal start/stop code
paths so pause/resume is reliable.

In
`@packages/web-platform/web-elements/src/elements/XMarkdown/XMarkdownAttributes.ts`:
- Around line 1255-1259: The code moves the original truncation/cursor node from
light DOM into the shadow by using span.appendChild(view), which removes it from
its source and prevents reuse; instead, when (cfg as any).truncationType ===
'view' and a view node is found via this.#dom.querySelector(String((cfg as
any).content)), append a deep clone (e.g., view.cloneNode(true)) or slot the
original into the shadow rather than calling appendChild(view) so the source
node remains in light DOM and can be reused across rerenders; update the logic
around span.appendChild(view) (and the analogous block around lines 1300-1305)
to use cloning or a slot mechanism and ensure any event listeners or references
are correctly handled for the cloned element.
- Around line 661-668: When content is cleared in `#render`(), you must also reset
the typewriter animation state so timers and cursor/step state don't persist; in
addition to clearing DOM and setting `#renderedContent` and `#appendRemainder`, call
the internal reset/stop logic (e.g. invoke any existing method like
`#clearAppendFlushTimer` and also stop/clear the typewriter timer, reset cursor
visibility and step counters — e.g. `#stopTypewriter`(), clear `#appendFlushTimer`,
set cursor/visibility flags and step/index fields to their initial values) so no
interval or cursor state leaks into the next render.
- Around line 1111-1178: Handlers _handleMarkdownStyle, _handleContentComplete,
_handleTypewriterDynamicHeight, _handleTypewriterHeightTransition, and
_handleTextMaxline only update cached state or style but do not trigger the
post-render fixes, leaving the DOM stale; after updating their internal state
and calling `#updateStyleTag`() (where present), call the component's post-render
routine (this.#applyPostRenderPolicies()) and/or schedule a render
(this.#scheduleRender()) so cursor/truncation/height behaviors are recomputed
immediately; modify each handler to invoke these methods (use
`#applyPostRenderPolicies`() first, then `#scheduleRender`() if needed) to force
reapplication of post-render policies when those attributes change at runtime.

---

Nitpick comments:
In `@packages/web-platform/web-elements/src/elements/XMarkdown/XMarkdown.ts`:
- Around line 174-177: Multiple methods (including getImages) re-query
shadowRoot for '#markdown-root' with ad-hoc null checks; replace these with a
single cached genDomGetter instance to centralize safe Shadow DOM access. Add a
private cached getter (e.g., this._getMarkdownRoot =
genDomGetter('#markdown-root')) on the component and refactor getImages and the
other methods that query '#markdown-root' (the blocks around lines 208-223 and
301-307) to call that getter, handle null/HTMLElement typing consistently, and
remove the repeated shadowRoot?.querySelector logic.

In `@packages/web-platform/web-elements/tests/x-markdown.spec.ts`:
- Around line 349-350: The test uses fixed sleeps (e.g.,
page.waitForTimeout(50)) after helper selectShadowText which makes tests flaky;
replace these waits with observable waits that assert the actual DOM/selection
state instead. Locate calls to selectShadowText and the subsequent
page.waitForTimeout (including the other occurrences referenced) and change them
to await page.waitForFunction(...) or await expect(<appropriate
Locator>).toHaveText/toHaveClass/visibility checks tied to the exact DOM
mutation or selection change you need (or use locator.waitFor when waiting for
an element state). Ensure the wait checks the concrete condition produced by
selectShadowText (selection range text, attribute change, or element content)
rather than sleeping for a fixed duration.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 933f1811-b0c3-45b8-ab20-702215e6c72a

📥 Commits

Reviewing files that changed from the base of the PR and between 4bb3cb5 and a56b8d3.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (29)
  • .changeset/nasty-lizards-refuse.md
  • .github/lynx-stack.instructions.md
  • packages/web-platform/web-elements/index.css
  • packages/web-platform/web-elements/package.json
  • packages/web-platform/web-elements/src/elements/XMarkdown/XMarkdown.ts
  • packages/web-platform/web-elements/src/elements/XMarkdown/XMarkdownAttributes.ts
  • packages/web-platform/web-elements/src/elements/XMarkdown/index.ts
  • packages/web-platform/web-elements/src/elements/XMarkdown/x-markdown.css
  • packages/web-platform/web-elements/src/elements/all.ts
  • packages/web-platform/web-elements/src/elements/htmlTemplates.ts
  • packages/web-platform/web-elements/tests/fixtures/shell-project.ts
  • packages/web-platform/web-elements/tests/fixtures/x-markdown/basic.html
  • packages/web-platform/web-elements/tests/fixtures/x-markdown/events.html
  • packages/web-platform/web-elements/tests/fixtures/x-markdown/html-tags.html
  • packages/web-platform/web-elements/tests/fixtures/x-markdown/image.html
  • packages/web-platform/web-elements/tests/fixtures/x-markdown/incremental.html
  • packages/web-platform/web-elements/tests/fixtures/x-markdown/inlineview-class.html
  • packages/web-platform/web-elements/tests/fixtures/x-markdown/inlineview.html
  • packages/web-platform/web-elements/tests/fixtures/x-markdown/menu.html
  • packages/web-platform/web-elements/tests/fixtures/x-markdown/style.html
  • packages/web-platform/web-elements/tests/fixtures/x-markdown/table.html
  • packages/web-platform/web-elements/tests/fixtures/x-markdown/text-selection.html
  • packages/web-platform/web-elements/tests/fixtures/x-markdown/truncate.html
  • packages/web-platform/web-elements/tests/fixtures/x-markdown/typewriter-cursor.html
  • packages/web-platform/web-elements/tests/fixtures/x-markdown/typewriter-effect.html
  • packages/web-platform/web-elements/tests/fixtures/x-markdown/typewriter-keep-cursor.html
  • packages/web-platform/web-elements/tests/fixtures/x-markdown/typewriter-trailing-text.html
  • packages/web-platform/web-elements/tests/fixtures/x-markdown/typewriter.html
  • packages/web-platform/web-elements/tests/x-markdown.spec.ts
✅ Files skipped from review due to trivial changes (20)
  • packages/web-platform/web-elements/tests/fixtures/shell-project.ts
  • packages/web-platform/web-elements/src/elements/all.ts
  • packages/web-platform/web-elements/tests/fixtures/x-markdown/basic.html
  • packages/web-platform/web-elements/tests/fixtures/x-markdown/inlineview.html
  • packages/web-platform/web-elements/tests/fixtures/x-markdown/image.html
  • packages/web-platform/web-elements/src/elements/XMarkdown/index.ts
  • packages/web-platform/web-elements/src/elements/XMarkdown/x-markdown.css
  • .github/lynx-stack.instructions.md
  • packages/web-platform/web-elements/tests/fixtures/x-markdown/inlineview-class.html
  • packages/web-platform/web-elements/tests/fixtures/x-markdown/table.html
  • packages/web-platform/web-elements/tests/fixtures/x-markdown/incremental.html
  • packages/web-platform/web-elements/tests/fixtures/x-markdown/style.html
  • packages/web-platform/web-elements/tests/fixtures/x-markdown/truncate.html
  • packages/web-platform/web-elements/tests/fixtures/x-markdown/typewriter-trailing-text.html
  • packages/web-platform/web-elements/tests/fixtures/x-markdown/typewriter-cursor.html
  • packages/web-platform/web-elements/tests/fixtures/x-markdown/html-tags.html
  • packages/web-platform/web-elements/tests/fixtures/x-markdown/typewriter-effect.html
  • packages/web-platform/web-elements/tests/fixtures/x-markdown/typewriter.html
  • packages/web-platform/web-elements/tests/fixtures/x-markdown/typewriter-keep-cursor.html
  • packages/web-platform/web-elements/src/elements/htmlTemplates.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/web-platform/web-elements/tests/fixtures/x-markdown/events.html
  • packages/web-platform/web-elements/package.json
  • packages/web-platform/web-elements/tests/fixtures/x-markdown/menu.html

Comment thread packages/web-platform/web-elements/src/elements/XMarkdown/XMarkdown.ts Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (6)
packages/web-platform/web-elements/src/elements/XMarkdown/XMarkdownAttributes.ts (4)

1119-1140: ⚠️ Potential issue | 🟠 Major

These attribute updates stay stale until another render happens.

The handlers update internal state or the style tag, but truncation markers, cursor behavior, overflow emission, and dynamic-height transition are only recalculated inside #applyPostRenderPolicies(). Toggling these attributes after first paint leaves the DOM in the old state until some unrelated rerender occurs.

Also applies to: 1165-1186

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/web-platform/web-elements/src/elements/XMarkdown/XMarkdownAttributes.ts`
around lines 1119 - 1140, The attribute handlers (_handleMarkdownStyle,
_handleContentId, _handleTextSelection) currently only update internal state or
style tag so UI behaviors (truncation markers, cursor, overflow emission,
dynamic-height transitions) remain stale; call or schedule a post-render
recalculation by invoking the existing `#applyPostRenderPolicies`() after updating
state (or, if timing matters, queue it with requestAnimationFrame/microtask) and
keep the existing `#updateStyleTag`() call in _handleTextSelection; ensure
`#applyMarkdownStyle`, `#contentId` and `#textSelection` assignments are done before
calling `#applyPostRenderPolicies`() so the recalculation uses the new values.

674-681: ⚠️ Potential issue | 🟠 Major

Reset the typewriter session when content changes or clears.

These paths keep the previous timer/cursor/progress alive. Clearing content can leave the old animation running, and replacing content in typewriter mode reuses the old #animationStep, so the next payload can start mid-stream or finish immediately.

Also applies to: 1098-1106


1263-1268: ⚠️ Potential issue | 🟠 Major

Clone light-DOM views before injecting them into truncation/cursor containers.

Both branches appendChild(view) the original node. After the next rerender or cursor removal, that source view is no longer in light DOM, so the same custom truncation view/cursor cannot be injected again.

Also applies to: 1308-1314

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/web-platform/web-elements/src/elements/XMarkdown/XMarkdownAttributes.ts`
around lines 1263 - 1268, The truncation/cursor injection currently does
span.appendChild(view) which moves the original light-DOM node out of source,
preventing reuse; change both places that append the found view (the branch
where (cfg as any).truncationType === 'view' and the similar cursor-injection
block) to append a cloned node instead by using view.cloneNode(true) (deep
clone) before appending to span so the original light-DOM view remains intact
for future renders.

1150-1157: ⚠️ Potential issue | 🟠 Major

Treat non-positive animation-velocity as an actual pause/stop.

XMarkdown.pauseAnimation() writes animation-velocity="0", but Line 1153 ignores 0 and leaves the running interval untouched. That makes the public pause API ineffective while the typewriter is active.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/web-platform/web-elements/src/elements/XMarkdown/XMarkdownAttributes.ts`
around lines 1150 - 1157, _handleAnimationVelocity currently ignores zero so
XMarkdown.pauseAnimation (which writes animation-velocity="0") doesn't stop the
typewriter; change the handler to parse newVal into n, if Number.isNaN(n)
return, otherwise assign this.#animationVelocity = n; then if
this.#animationType === 'typewriter' and this.#animationTimer: if n > 0 call
this.#startTypewriterTimer() else stop the timer (clear the interval/timeout and
set this.#animationTimer = null) so a non-positive velocity properly
pauses/stops the animation.
packages/web-platform/web-elements/src/elements/XMarkdown/XMarkdown.ts (2)

251-304: ⚠️ Potential issue | 🟠 Major

Honor indexType: 'source' or remove it from the API.

Line 252 advertises source-based indexing, but Lines 294-299 always build the range against rendered text nodes. As soon as the markdown contains syntax characters, "source" offsets point at the wrong substring and the returned rect is wrong.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/web-platform/web-elements/src/elements/XMarkdown/XMarkdown.ts`
around lines 251 - 304, getTextBoundingRect advertises an indexType:'source'
option but the current implementation always treats start/end as rendered
character offsets; either implement source→rendered mapping or remove the
option. Fix by updating getTextBoundingRect (and the helper createRangeByChar)
to check params.indexType: if 'source' convert the provided source offsets to
rendered-text offsets (e.g., walk the original markdown/source-to-render mapping
and translate start/end into the positions used by createRangeByChar) before
calling createRangeByChar; if you don't want to support 'source' remove
indexType from the signature and any callers. Ensure you reference
getTextBoundingRect, params.indexType, and createRangeByChar when making the
change so the correct conversion or API removal is applied.

335-375: ⚠️ Potential issue | 🟠 Major

getParseResult() truncates ranges for nested markup.

calcOffset() stops on the first direct text-node hit, so tags whose rendered text spans multiple descendant nodes only report the first fragment. A node like <p>Hello <strong>world</strong></p> will under-report end.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/web-platform/web-elements/src/elements/XMarkdown/XMarkdown.ts`
around lines 335 - 375, getParseResult's calcOffset only captures the first text
fragment because it breaks as soon as it finds a text node that is the target or
its direct child; change calcOffset (used in getParseResult) to scan all text
nodes under root and record the position of the first text node that is inside
the target node and the position at the end of the last text node that is inside
the target (use node.nodeType === Node.TEXT_NODE to detect text nodes and
node.contains(current) to test descendant membership). In short: when iterating
the TreeWalker, don't break on the first match—set start on the first matching
text node and keep updating end for each subsequent matching text node so
elements like <p>Hello <strong>world</strong></p> return a start at "Hello" and
end after "world".
🧹 Nitpick comments (1)
packages/web-platform/web-elements/tests/x-markdown.spec.ts (1)

185-209: Keep the event test hermetic.

This case clicks a live https://example.com link. If the component ever stops preventing default, the test starts depending on external navigation instead of just validating bindlink/bindimageTap. Stub or route that URL before clicking.

As per coding guidelines: "Mock external requests in tests using page.route to ensure tests are hermetic and fast".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/web-platform/web-elements/tests/x-markdown.spec.ts` around lines 185
- 209, The test clicks a real external link which can cause navigation; before
the click in the 'should fire bindlink and bindimageTap events' test, register a
Playwright route (using page.route) for the target URL (e.g.,
'https://example.com/*') to intercept and fulfill a synthetic response (200 +
minimal body) so the click stays in-process; make the page.route call prior to
calling markdown.locator('a').click() and remove or dispose the route after the
assertions to keep the test hermetic and ensure the bindlink/bindimageTap
assertions run reliably.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@packages/web-platform/web-elements/src/elements/XMarkdown/XMarkdownAttributes.ts`:
- Around line 560-576: dispose() currently only removes event listeners but
leaves timers running; update the dispose() method (the same function shown as
dispose()) to clear any pending timers by calling clearTimeout/clearInterval for
`#appendFlushTimer` and `#animationTimer` (and any other timer fields such as a
typewriter interval if present), and then null/undefined those fields so they
cannot fire after the element is detached; keep the existing event tear-down
logic and perform timer clearing before or alongside setting
`#eventsAttached/`#selectionEventAttached to false.

---

Duplicate comments:
In `@packages/web-platform/web-elements/src/elements/XMarkdown/XMarkdown.ts`:
- Around line 251-304: getTextBoundingRect advertises an indexType:'source'
option but the current implementation always treats start/end as rendered
character offsets; either implement source→rendered mapping or remove the
option. Fix by updating getTextBoundingRect (and the helper createRangeByChar)
to check params.indexType: if 'source' convert the provided source offsets to
rendered-text offsets (e.g., walk the original markdown/source-to-render mapping
and translate start/end into the positions used by createRangeByChar) before
calling createRangeByChar; if you don't want to support 'source' remove
indexType from the signature and any callers. Ensure you reference
getTextBoundingRect, params.indexType, and createRangeByChar when making the
change so the correct conversion or API removal is applied.
- Around line 335-375: getParseResult's calcOffset only captures the first text
fragment because it breaks as soon as it finds a text node that is the target or
its direct child; change calcOffset (used in getParseResult) to scan all text
nodes under root and record the position of the first text node that is inside
the target node and the position at the end of the last text node that is inside
the target (use node.nodeType === Node.TEXT_NODE to detect text nodes and
node.contains(current) to test descendant membership). In short: when iterating
the TreeWalker, don't break on the first match—set start on the first matching
text node and keep updating end for each subsequent matching text node so
elements like <p>Hello <strong>world</strong></p> return a start at "Hello" and
end after "world".

In
`@packages/web-platform/web-elements/src/elements/XMarkdown/XMarkdownAttributes.ts`:
- Around line 1119-1140: The attribute handlers (_handleMarkdownStyle,
_handleContentId, _handleTextSelection) currently only update internal state or
style tag so UI behaviors (truncation markers, cursor, overflow emission,
dynamic-height transitions) remain stale; call or schedule a post-render
recalculation by invoking the existing `#applyPostRenderPolicies`() after updating
state (or, if timing matters, queue it with requestAnimationFrame/microtask) and
keep the existing `#updateStyleTag`() call in _handleTextSelection; ensure
`#applyMarkdownStyle`, `#contentId` and `#textSelection` assignments are done before
calling `#applyPostRenderPolicies`() so the recalculation uses the new values.
- Around line 1263-1268: The truncation/cursor injection currently does
span.appendChild(view) which moves the original light-DOM node out of source,
preventing reuse; change both places that append the found view (the branch
where (cfg as any).truncationType === 'view' and the similar cursor-injection
block) to append a cloned node instead by using view.cloneNode(true) (deep
clone) before appending to span so the original light-DOM view remains intact
for future renders.
- Around line 1150-1157: _handleAnimationVelocity currently ignores zero so
XMarkdown.pauseAnimation (which writes animation-velocity="0") doesn't stop the
typewriter; change the handler to parse newVal into n, if Number.isNaN(n)
return, otherwise assign this.#animationVelocity = n; then if
this.#animationType === 'typewriter' and this.#animationTimer: if n > 0 call
this.#startTypewriterTimer() else stop the timer (clear the interval/timeout and
set this.#animationTimer = null) so a non-positive velocity properly
pauses/stops the animation.

---

Nitpick comments:
In `@packages/web-platform/web-elements/tests/x-markdown.spec.ts`:
- Around line 185-209: The test clicks a real external link which can cause
navigation; before the click in the 'should fire bindlink and bindimageTap
events' test, register a Playwright route (using page.route) for the target URL
(e.g., 'https://example.com/*') to intercept and fulfill a synthetic response
(200 + minimal body) so the click stays in-process; make the page.route call
prior to calling markdown.locator('a').click() and remove or dispose the route
after the assertions to keep the test hermetic and ensure the
bindlink/bindimageTap assertions run reliably.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a79d4af1-fe55-4415-a8e9-cf12445c2a8b

📥 Commits

Reviewing files that changed from the base of the PR and between a56b8d3 and 922effe.

📒 Files selected for processing (12)
  • packages/web-platform/web-constants/src/utils/index.ts
  • packages/web-platform/web-constants/src/utils/setElementPropertyOrAttribute.ts
  • packages/web-platform/web-core-wasm/package.json
  • packages/web-platform/web-core-wasm/ts/client/mainthread/crossThreadHandlers/registerSetNativePropsHandler.ts
  • packages/web-platform/web-core-wasm/ts/client/mainthread/elementAPIs/createElementAPI.ts
  • packages/web-platform/web-core/src/uiThread/crossThreadHandlers/registerSetNativePropsHandler.ts
  • packages/web-platform/web-elements/src/elements/XMarkdown/XMarkdown.ts
  • packages/web-platform/web-elements/src/elements/XMarkdown/XMarkdownAttributes.ts
  • packages/web-platform/web-elements/tests/x-markdown.spec.ts
  • packages/web-platform/web-mainthread-apis/ts/createMainThreadGlobalThis.ts
  • packages/web-platform/web-tests/tests/react.spec.ts
  • packages/web-platform/web-tests/tests/react/basic-element-x-markdown-markdown-style/index.jsx
✅ Files skipped from review due to trivial changes (3)
  • packages/web-platform/web-constants/src/utils/index.ts
  • packages/web-platform/web-core-wasm/package.json
  • packages/web-platform/web-tests/tests/react/basic-element-x-markdown-markdown-style/index.jsx

@Sherry-hue Sherry-hue force-pushed the p/hw/support-x-markdown branch 2 times, most recently from 9558773 to a9c7861 Compare April 13, 2026 13:31
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 13, 2026

Codecov Report

❌ Patch coverage is 84.58835% with 307 lines in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
...ents/src/elements/XMarkdown/XMarkdownAttributes.ts 78.41% 307 Missing ⚠️

📢 Thoughts on this report? Let us know!

@Sherry-hue Sherry-hue force-pushed the p/hw/support-x-markdown branch 3 times, most recently from e1bc786 to 08c7221 Compare April 14, 2026 06:41
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Apr 14, 2026

Merging this PR will degrade performance by 43.4%

❌ 3 regressed benchmarks
✅ 78 untouched benchmarks
⏩ 26 skipped benchmarks1

⚠️ Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Benchmark BASE HEAD Efficiency
basic-performance-scroll-view-100 8 ms 8.5 ms -6.79%
transform 1000 view elements 40.8 ms 47.6 ms -14.44%
basic-performance-text-200 11.7 ms 20.7 ms -43.4%

Comparing Sherry-hue:p/hw/support-x-markdown (b382f1e) with main (5c39654)

Open in CodSpeed

Footnotes

  1. 26 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@relativeci
Copy link
Copy Markdown

relativeci Bot commented Apr 14, 2026

React External

#508 Bundle Size — 580.35KiB (0%).

b382f1e(current) vs ad985f1 main#506(baseline)

Bundle metrics  no changes
                 Current
#508
     Baseline
#506
No change  Initial JS 0B 0B
No change  Initial CSS 0B 0B
No change  Cache Invalidation 0% 0%
No change  Chunks 0 0
No change  Assets 3 3
No change  Modules 17 17
No change  Duplicate Modules 5 5
No change  Duplicate Code 8.59% 8.59%
No change  Packages 0 0
No change  Duplicate Packages 0 0
Bundle size by type  no changes
                 Current
#508
     Baseline
#506
No change  Other 580.35KiB 580.35KiB

Bundle analysis reportBranch Sherry-hue:p/hw/support-x-markdo...Project dashboard


Generated by RelativeCIDocumentationReport issue

@relativeci
Copy link
Copy Markdown

relativeci Bot commented Apr 14, 2026

React MTF Example

#524 Bundle Size — 193.94KiB (0%).

b382f1e(current) vs ad985f1 main#522(baseline)

Bundle metrics  no changes
                 Current
#524
     Baseline
#522
No change  Initial JS 0B 0B
No change  Initial CSS 0B 0B
No change  Cache Invalidation 0% 0%
No change  Chunks 0 0
No change  Assets 3 3
No change  Modules 173 173
No change  Duplicate Modules 66 66
No change  Duplicate Code 43.94% 43.94%
No change  Packages 2 2
No change  Duplicate Packages 0 0
Bundle size by type  no changes
                 Current
#524
     Baseline
#522
No change  IMG 111.23KiB 111.23KiB
No change  Other 82.71KiB 82.71KiB

Bundle analysis reportBranch Sherry-hue:p/hw/support-x-markdo...Project dashboard


Generated by RelativeCIDocumentationReport issue

@relativeci
Copy link
Copy Markdown

relativeci Bot commented Apr 14, 2026

React Example

#7391 Bundle Size — 223.33KiB (0%).

b382f1e(current) vs ad985f1 main#7389(baseline)

Bundle metrics  no changes
                 Current
#7391
     Baseline
#7389
No change  Initial JS 0B 0B
No change  Initial CSS 0B 0B
No change  Cache Invalidation 0% 0%
No change  Chunks 0 0
No change  Assets 4 4
No change  Modules 179 179
No change  Duplicate Modules 69 69
No change  Duplicate Code 44.48% 44.48%
No change  Packages 2 2
No change  Duplicate Packages 0 0
Bundle size by type  no changes
                 Current
#7391
     Baseline
#7389
No change  IMG 145.76KiB 145.76KiB
No change  Other 77.58KiB 77.58KiB

Bundle analysis reportBranch Sherry-hue:p/hw/support-x-markdo...Project dashboard


Generated by RelativeCIDocumentationReport issue

@relativeci
Copy link
Copy Markdown

relativeci Bot commented Apr 14, 2026

Web Explorer

#8965 Bundle Size — 898.09KiB (+19.82%).

b382f1e(current) vs ad985f1 main#8963(baseline)

Warning

Bundle introduced 7 new packages: markdown-it, dompurify, mdurl and 4 more – View changed packages

Bundle metrics  Change 7 changes Regression 2 regressions
                 Current
#8965
     Baseline
#8963
Regression  Initial JS 44.47KiB(+0.05%) 44.45KiB
No change  Initial CSS 2.16KiB 2.16KiB
Change  Cache Invalidation 31.39% 0%
Change  Chunks 9(+12.5%) 8
Change  Assets 11(+10%) 10
Change  Modules 231(+55.03%) 149
No change  Duplicate Modules 11 11
Change  Duplicate Code 27.21%(-22.21%) 34.98%
Regression  Packages 10(+233.33%) 3
No change  Duplicate Packages 0 0
Bundle size by type  Change 1 change Regression 1 regression
                 Current
#8965
     Baseline
#8963
Regression  JS 494.3KiB (+42.96%) 345.76KiB
No change  Other 401.63KiB 401.63KiB
No change  CSS 2.16KiB 2.16KiB

Bundle analysis reportBranch Sherry-hue:p/hw/support-x-markdo...Project dashboard


Generated by RelativeCIDocumentationReport issue

@Sherry-hue Sherry-hue force-pushed the p/hw/support-x-markdown branch 7 times, most recently from c75c721 to b2e0e5f Compare April 16, 2026 08:50
@Sherry-hue Sherry-hue force-pushed the p/hw/support-x-markdown branch from b2e0e5f to 191a4a8 Compare April 17, 2026 06:32
@Sherry-hue Sherry-hue force-pushed the p/hw/support-x-markdown branch from 191a4a8 to b382f1e Compare April 17, 2026 08:46
@Sherry-hue Sherry-hue changed the title P/hw/support x markdown XMarkdown Web supports Apr 20, 2026
@Sherry-hue Sherry-hue changed the title XMarkdown Web supports feat : XMarkdown Web supports Apr 20, 2026
@Sherry-hue Sherry-hue changed the title feat : XMarkdown Web supports feat(x-markdown): add markdown rendering support and tests Apr 20, 2026
@Sherry-hue Sherry-hue merged commit fb7bc84 into lynx-family:main Apr 20, 2026
106 of 116 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants